package tests;

import static org.junit.Assert.*;

import model.TurtleState;
import model.parser.Parser;
import model.parser.ParserException;
import model.parser.ParserException.BadArgumentsException;
import model.parser.ParserException.BadSyntaxException;
import model.parser.ParserException.BadTokenException;
import model.semantics.Node;
import model.turtle.Position;

import java.io.File;
import java.util.*;

import org.junit.Before;
import org.junit.Test;

public class ParserTest {
    private static final String TESTFILE_NAME = "src/tests/parser_test_file2";
    private static final char COMMENT_CHAR = '#';
    private static final String DELIMITER = "\\s+";
    private static final String BAD_ARGUMENTS_STRING = "BAD_ARGUMENTS";
    private static final String BAD_SYNTAX_STRING = "BAD_SYNTAX";
    private static final String BAD_TOKEN_STRING = "BAD_TOKEN";
    private static final String END_EXPRESSION_STRING = "%%";

    private List<Position> myInput;
    private List<String> myExpression;
    private List<Output> myOutput;
    private Parser<Integer> myParser;

    public enum ErrorType {
        BAD_ARGUMENTS, BAD_SYNTAX, BAD_TOKEN
    }

    // Poor man's union in Java.
    private static class Output {
    }

    private static class PositionOutput extends Output {
        List<Position> position;

        public PositionOutput(List<Position> _position) {
            position = _position;
        }
    }

    private static class ErrorOutput extends Output {

        public ErrorType type;

        public ErrorOutput(ErrorType _type) {
            type = _type;
        }
    }

    private static final int INPUT = 0, EXPRESSION = 1, OUTPUT = 2;

    @Before
    public void setUp() throws Exception {
        myParser = new Parser<Integer>("token", "syntax");
        myInput = new ArrayList<Position>();
        myExpression = new ArrayList<String>();
        myOutput = new ArrayList<Output>();

        // Load test file.
        Scanner scan = new Scanner(new File(TESTFILE_NAME));

        String line;
        StringBuilder expressionStringBuilder = new StringBuilder();
        int mode = INPUT;
        while (scan.hasNextLine()) {
            line = scan.nextLine();

            if (line.isEmpty() || line.charAt(0) == COMMENT_CHAR
                    || line.matches(DELIMITER))
                continue;

            if (mode == INPUT) {
                String[] tokens = line.split(DELIMITER);
                int x = Integer.parseInt(tokens[0]);
                int y = Integer.parseInt(tokens[1]);
                int d = Integer.parseInt(tokens[2]);
                Position p = new Position(x, y, d);
                myInput.add(p);
            } else if (mode == EXPRESSION) {
                if (line.equals(END_EXPRESSION_STRING)) {
                    mode = OUTPUT;
                    myExpression.add(expressionStringBuilder.toString());
                    expressionStringBuilder = new StringBuilder();
                    continue;
                } else {
                    expressionStringBuilder.append(line + "\n");
                }
            } else {
                if (line.equals(BAD_ARGUMENTS_STRING)) {
                    myOutput.add(new ErrorOutput(ErrorType.BAD_ARGUMENTS));
                } else if (line.equals(BAD_SYNTAX_STRING)) {
                    myOutput.add(new ErrorOutput(ErrorType.BAD_SYNTAX));
                } else if (line.equals(BAD_TOKEN_STRING)) {
                    myOutput.add(new ErrorOutput(ErrorType.BAD_TOKEN));
                } else {
                    String[] tokens = line.split(DELIMITER);
                    List<Position> positions = new ArrayList<Position>();
                    for(int i = 0; i < tokens.length/3; i++) {
	                    int x = Integer.parseInt(tokens[0]);
	                    int y = Integer.parseInt(tokens[1]);
	                    int d = Integer.parseInt(tokens[2]);
	                    Position p = new Position(x, y, d);
	                    positions.add(p);
                    }
                    myOutput.add(new PositionOutput(positions));
                }
            }
            if (mode != EXPRESSION) {
                mode = (mode + 1) % 3;
            }
        }

    }

    @Test
    public void testParse() {
        for (int i = 0; i < myInput.size(); i++) {
            Position in = myInput.get(i);
            System.out.println(myExpression.get(i));
            Node<Integer> root = null;
            try {
                root = myParser.parse(myExpression.get(i));
            } catch (ParserException e) {
                if (!(myOutput.get(i) instanceof ErrorOutput))
                    assertTrue("Query " + i, false);
                ErrorType type = ((ErrorOutput) myOutput.get(i)).type;
                if ((e instanceof BadArgumentsException)
                        && type != ErrorType.BAD_ARGUMENTS) {
                    assertTrue("Query " + i, false);
                }
                if ((e instanceof BadSyntaxException)
                        && type != ErrorType.BAD_SYNTAX) {
                    assertTrue("Query " + i, false);
                }
                if ((e instanceof BadTokenException)
                        && type != ErrorType.BAD_TOKEN) {
                    assertTrue("Query " + i, false);
                }
                continue;
            }
            System.out.println(root.toString());
            TurtleState state = new TurtleState();
            PositionOutput position = (PositionOutput) myOutput.get(i);

            List<Integer> allTurtles = new ArrayList<Integer>();
            for(int j = 1; j <= position.position.size(); j++) {
            	allTurtles.add(j);
            }
        	state.setTurtles(allTurtles);
            for(int j = 0; j < position.position.size(); j++) {
            	state.getTurtles().get(j).setPosition(in);
            }
            root.evaluate(state);
            
            for(int j = 0; j < position.position.size(); j++) {
	            Position out = state.getTurtles().get(j).getPosition();
	            assertEquals("Query " + i,
	                    position.position.get(j), out);
            }
        }
    }

}